// 到typescript的精髓了 type的实现
// Generics- Type which take parameters
// keyof typeof Type['a'] syntax to access a subset of type
// Conditional Types
// Mapped Type 

/******************Generics 泛型**************************** */
function identity(arg: number): number {
    return arg;
}

function identity1(arg: any): any {
    return arg;
}

function identity2<Type>(arg: Type): Type {
    return arg;
}
// let output: string
let output = identity2<string>("myString");
let output1 = identity2("myString");

function loggingIdentity<Type>(arg: Type): Type {
    // Property 'length' does not exist on type 'Type'
    // console.log(arg.length)
    return arg;
}
function loggingIdentity1<Type>(arg: Type[]): Type[] {
    // Property 'length' does not exist on type 'Type'
    console.log(arg.length)
    return arg;
}

function loggingIdentity2<Type>(arg: Array<Type>): Array<Type> {
    // Property 'length' does not exist on type 'Type'
    console.log(arg.length)
    return arg;
}

// Generic Types
let myIdentity: <Type>(arg: Type) => Type = identity2;

// 换个名字
function identity3<Input>(arg: Input):Input {
    return arg;
}

let myIdentity1: <Input>(arg: Input) => Input = identity3;
let myIdentity2: {<Type>(arg:Type):Type} = identity2;

interface GenericIdentityFn {
    <Type>(arg: Type):Type;
}

let myIdentity3: GenericIdentityFn = identity2;


interface GenericIdentityF<Type> {
    <Type>(arg: Type):Type;
}

let myIdentity4: GenericIdentityF<number> = identity2;
myIdentity4;

// Generic Class
class GenericNumber<NumType> {
    zeroValue:NumType;
    add:(x: NumType, y: NumType) => NumType;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) {
    return x + y;
}

let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) {
    return x + y;
}
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
// Generic Constrains

interface Lengthwise {
    length: number;
}
// create an interface that describes our constraint.
function loggingIdentity3<Type extends Lengthwise>(arg: Type): Type {
    console.log(arg.length)
    return arg;
}
// Argument of type 'number' is not assignable to parameter of type 'Lengthwise'.
// loggingIdentity3(4);

loggingIdentity3({length: 4, value: 3});

// Using Type Parameters in Generic Constraints
function getProperty<Type, Key extends keyof Type>(obj: Type, key:Key) {
    return obj[key];
}

let x = {a: 1, b: 2, c: 3, d: 4};
getProperty(x, "a");
// Argument of type '"m"' is not assignable to parameter of type '"a" | "b" | "c" | "d"'
getProperty(x, "m");

// Using Class Types in Generics
function create<Type>(c:{new ():Type}): Type {
    return new c();
}
 
class BeeKeeper {
    hasMask:boolean = true;
}

class ZooKeeper {
    nametag: string = "Mikle";
}


class Animal {
    numLegs: number = 4
}

class Bee extends Animal {
    numLegs = 6;
    keeper: BeeKeeper = new BeeKeeper();
}

class Lion extends Animal {
    keeper: ZooKeeper = new ZooKeeper();
}

function createInstance<A extends Animal>(c:new () => A):A {
    return new c();
}

createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;


type Container<T, U> = {
    element:T;
    children: U;
}

// Generic Parameter Defaults
declare function create1(): Container<HTMLDivElement, HTMLDivElement[]>;
declare function create1<T extends HTMLDivElement>(element: T): Container<T, T[]>;
declare function create1<T extends HTMLElement, U extends HTMLElement>(
    element: T,
    children: U[]
): Container<T, U[]>;

declare function create1<T extends HTMLElement = HTMLDivElement, U = T[]> (
    element?: T,
    children?: U
): Container<T, U>;
// function create1(): Container<HTMLDivElement, HTMLDivElement[]> (+3 overloads)
const div = create1();
// const p: Container<HTMLParagraphElement, HTMLParagraphElement[]>
const p = create1(new HTMLParagraphElement());

//***********keyof type operator********** */
type Point = {x: number, y: number};
// type p = keyof Point as type p = "x" | "y"
type p = keyof Point;
type Arrayish = {[n:number]: unknown};
// type A = number
type A = keyof Arrayish;

type Mapish = {[k:string]: boolean}
// type M = string | number
type M = keyof Mapish;


/*************Type of Operator************* */
// print string
console.log(typeof "Hello World");
let s = "Hello";
// let n: string
let n:typeof s

type Predicate = (x: unknown) => boolean;
// type k = boolean
type k = ReturnType<Predicate>;

function f() {
    return {x: 0, y: 3};
}
// 'f' refers to a value, but is being used as a type here. Did you mean 'typeof f'?
type P = ReturnType<f>;
// type P1 = {
//     x: number;
//     y: number;
// }
type P1 = ReturnType<typeof f>

declare const msgbox:(prompt:string) => boolean;
// ',' expected.ts(1005)
let shouldContinue: typeof msgbox("jjjjjj");
let shouldContinue1: ReturnType<typeof msgbox>;

/*********** indexed access types**************** */
type Person = {age: number, name: string, alive: boolean};
// type Age = number
type Age = Person["age"];
// type T1 = string | number
type T1 = Person["age" | "name"];
// type T2 = string | number | boolean
type T2 = Person[keyof Person];
type AliveOrName = "alive" | "name";
// type T3 = string | boolean
type T3 = Person[AliveOrName]
// Property 'alve' does not exist on type 'Person'
type T1_ = Person["alve"]

const MyArray = [
    {name: "Alice", age: 15},
    {name: "Bob", age: 23},
    {name: "Eve", age: 38},
];
// type Person1 = {
//     name: string;
//     age: number;
// }
type Person1 = typeof MyArray[number];
// type Age1 = number
type Age1 = typeof MyArray[number]["age"];
// type Age2 = number
type Age2 = Person1["age"];

const key = "age";
// Type 'key' cannot be used as an index type
type Age3 = Person1[key];

type key = "age";
type Age4 = Person1[key];


/***************Conditinal Types************************** */
interface Animal {
    live(): void;
}

interface Dog extends Animal {
    woof(): void;
}
interface RegExp {}
// type Example1 = number
type Example1  = Dog extends Animal ? number : string;

// type Example2 = string
type Example2  = RegExp extends Animal ? number : string;

// SomeType extends OtherType ? TrueType : FalseType;

interface IdLabel {
    id: number /* some fields */
}

interface NameLabel {
    name: string /* other fields */
}

type NameOrId<T extends number | string> = T extends number ? IdLabel : NameLabel;
function createLabel(id: number): IdLabel;
function createLabel(name: string): NameLabel;
function createLabel(nameOrId: string | number):IdLabel | NameLabel;
// function createLabel(nameOrId: string | number):IdLabel | NameLabel{
//     throw "unimplemented";
// }



function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
    throw "unimplemented";
}
// let a: NameLabel
let a = createLabel("typescript");
// let b: IdLabel
let b = createLabel(1);
// let c: NameLabel | IdLabel
let c = createLabel(Math.random() ? "hello": 43);

// conditional type constraints
// Type '"message"' cannot be used to index type 'T'
type Message<T> = T["message"];

type MessageOf<T extends {message: unknown}> = T["message"];

interface Email {
    message: string;
}

// type EmailMessageContents = string
type EmailMessageContents = MessageOf<Email>;

type MessageOf1<T> = T extends {message: unknown} ? T["message"] : never;

interface Dog{
    bark(): void
}
// type EmailMessageContents1 = string
type EmailMessageContents1 = MessageOf1<Email>;
// type DogMessageContents = never
type DogMessageContents = MessageOf1<Dog>;

type Flatten<T> = T extends any[] ? T[number]:T
// Extracts out the element type;
// type Str = string
type Str = Flatten<string[]>;
type Str1 = Flatten<string>;

// Leaves the type alone;
// type Num = number
type Num = Flatten<number>;
// 推导类型， infer Item
type Flatten1<Type> = Type extends Array<infer Item> ? Item : Type;

type GetReturnType<Type> = Type extends(...args:never[]) => infer Return ? Return: never
// type Num1 = number
type Num1 = GetReturnType<()=> number>;
// type Str2 = string
type Str2 = GetReturnType<(x:string) => string>
// type Bools = boolean[]
type Bools = GetReturnType<(a: boolean, b: boolean) => boolean[]>;

declare function stringOrNumber(x:string): number;
declare function stringOrNumber(x:number): string;
declare function stringOrNumber(x:string | number): string | number;

// type T4 = string | number
type T4 = ReturnType<typeof stringOrNumber>

/**************Distributive Conditional Types**************** */
type ToArray<Type> = Type extends any ? Type[] : never;
// type StrArrOrNumArr = string[] | number[]
// what happens
//  ToArray<string> | ToArray<number>; ===> string[] | number[];
type StrArrOrNumArr = ToArray<string | number>;

type ToArrayNonDist<Type> = [Type] extends [any] ? Type[]: never;
// type ArrOfStrOrNum = (string | number)[]
type ArrOfStrOrNum = ToArrayNonDist<string | number>


// Mappped Types
interface Horse {}


type OnlyBoolsAndHorses = {
    [key:string]:boolean | Horse;
}

const conforms: OnlyBoolsAndHorses = {
    del: true,
    rodney: false
}

type OptionsFlags<Type> = {
    // 所以属性成为boolean
    [Property in keyof Type]: boolean;
}

type Features = {
    darkMode:() => void;
    newUserProfile:()=>void;
}
// type FeatureOptions = {
//     darkMode: boolean;
//     newUserProfile: boolean;
// }
type FeatureOptions = OptionsFlags<Features>;

// Mapping Modifiers
type CreateMutable<Type> = {
    -readonly [Property in keyof Type]: Type[Property];
};

type LockedAccount = {
    readonly id: string;
    readonly name: string;
}
// type UnlockedAccount = {
//     id: string;
//     name: string;
// }

type UnlockedAccount = CreateMutable<LockedAccount>


type Concrete<Type> = {
    [Property in keyof Type]-?: Type[Property];
}

type MaybeUser = {
    id: string;
    name?: string;
    age?: number;
}
// type User = {
//     id: string;
//     name: string;
//     age: number;
// }
type User = Concrete<MaybeUser>;

type Getters<Type> = {
    [Property in keyof Type as `get${Capitalize<string & Property>}`]:() => Type[Property];
};

interface Person2{
    name: string;
    age: number;
    location: string;
}
// type LazyPerson = {
//     getName: () => string;
//     getAge: () => number;
//     getLocation: () => string;
// }
type LazyPerson = Getters<Person2>;

type RemoveKindField<Type> = {
    [Property in keyof Type as Exclude<Property, "kind">]: Type[Property]
};

interface Circle {
    kind: "circle";
    raduis: number;
}
//
// type KindlessCircle = {
//     raduis: number;
// }
type KindlessCircle = RemoveKindField<Circle>

type EventConfig<Events extends {kind:string}> = {
    [E in Events as E["kind"]]:(event:E) => void;
};

type SquareEvent = {kind: "square", x: number, y: number};
type CircleEvent = {kind: "circle", radius: number};

type Config = EventConfig<SquareEvent | CircleEvent>;

type ExtractPII<Type> = {
    [Property in keyof Type]: Type[Property] extends {pii: true} ? true : false;
};

type DBFileds = {
    id: {format: "incrementing"};
    name: {type: string; pii: true};
}
/**
 * type ObjectsNeedingGDPRDeletion = {
    id: false;
    name: true;
}
 */
type ObjectsNeedingGDPRDeletion = ExtractPII<DBFileds>;
// Template Literal Types
type World = "world";
// type Greeting = "Hello world"
type Greeting = `Hello ${World}`;

type EmailLocaleIds = "welcome_email" | "email_heading";
type FooterLocaleIds = "footer_title" | "footer_sendoff";
// type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"
type AllLocaleIDs = `${EmailLocaleIds| FooterLocaleIds}_id`

type Lang = "en" | "ja" | "pt";
//type LocaleMessageIDs = "en_welcome_email_id" | "en_email_heading_id" | "en_footer_title_id" | "en_footer_sendoff_id"
//  | "ja_welcome_email_id" | "ja_email_heading_id" | "ja_footer_title_id" 
// | "ja_footer_sendoff_id" | "pt_welcome_email_id" | "pt_email_heading_id" | "pt_footer_title_id" | "pt_footer_sendoff_id"
type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;

const passedObject = {
    firstName: "Saoirse",
    lastName: "Ronan",
    age: 26,
}

declare function makeWatchedObject(obj:any) : any;

const person = makeWatchedObject({
    firstName: "Saoirse",
    lastName: "Ronan",
    age: 26,
})
// makeWatchedObject has added `on` to the anonymous Object
person.on("firstNameChange", (newValue)=> {
    console.log(`firstName was changed to ${newValue}`);
});

type PropEventSource<Type> = {
    on(eventName: `${string & keyof Type}Changed`, callback:(newValue:any) => void):void
}
// Create a "watched object" with an `on` method
// so that you can watch for changes to properties.

declare function makeWatchedObject1<Type>(obj: Type): Type & PropEventSource<Type>;

const person1 = makeWatchedObject1({
    firstName: "Saoirse",
    lastName: "Ronan",
    age: 26
});

person1.on("firstNameChanged", ()=>{});
// Prevent easy human error (using the key instead of the event name)
// Argument of type '"firstName"' is not assignable to parameter of type '"ageChanged" | "firstNameChanged" | "lastNameChanged"'
person1.on("firstName", ()=>{});

// it's typo-resistant
person1.on("frstNameChanged", () =>{})

type PropEventSource1<Type> = {
    on<key extends string & keyof Type>
        (eventName: `${key}Changed`, callback:(newValue: Type[key]) => void): void;
};

declare function makeWatchedObject2<Type>(obj: Type): Type & PropEventSource1<Type>;

const person2 = makeWatchedObject2({
    firstName: "Saoirse",
    lastName: "Ronan",
    age: 26
});
// (parameter) newValue: string
person2.on("firstNameChanged", newValue => {
    console.log(`new name is ${newValue.toUpperCase()}`);
});
// (parameter) newAge: number
person2.on("ageChanged", newAge=> {
    if (newAge < 0) {
        console.warn("warning! negative age");
    }
});

type Greeting1 = "Hello, world";
// type ShoutGreeting = "HELLO, WORLD"
type ShoutGreeting = Uppercase<Greeting1>;

type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`;
// type MainId = "ID-MY_APP"
type MainId = ASCIICacheKey<"my_app">;
//
// function applyStringMapping(symbol: Symbol, str: string) {
//     switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
//         case IntrinsicTypeKind.Uppercase: return str.toUpperCase();
//         case IntrinsicTypeKind.Lowercase: return str.toLowerCase();
//         case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1);
//         case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1);
//     }
//     return str;
// }